﻿using System;
using System.Collections.Generic;
using WiMo.Games;
using WiMo.Games.Drawables;
using WiMo.Games.Menus;
using WiMo.Games.Inputs;
using WiMo.Games.Audio;

namespace InvadersFromSpace
{
    public class Game : IGame
    {
        private const int INT_CONTINUE = 100;
        private const int INT_DIAGNOSTICS = 101;
        private const int INT_RESTART = 102;
        private const int INT_HELP = 103;
        private const int INT_CREDITS = 104;        
        private const int INT_EXIT = 999;
        

        private const int INT_CONCURRENT_BULLETS = 3;
        private const int INT_INVADER_TOP = 100;
        private const int INT_INVADER_ROWS = 3;
        private const int INT_INVADER_COLUMNS = 8;
        private const int INT_INVADER_SPACING = 20;

        private const int INT_ENEMY_BULLET_SPEED = 40;
        private const int INT_SHIP_MOVEMENT = 8;
        private const int INT_ENEMY_SPEED = 25;
        private const int INT_SHIP_BULLET_SPEED = 100;

        private ISpriteFont _titleFont;

        private List<ISpriteImage> _enemyBullets;

        private enum GameStates
        {
            StartUp,
            Active,
            BetweenRounds,
            GameOver,
            Help,
            Credits
        }

        private GameStates _gameState;
        private int _score;
        private int _round;

        IMenuComponent _mainMenu;
        IMenu _diagnostics;
        ISpriteImage _ship;
        ISpriteImage _bullet;
        ISpriteImage _spashImage;

        IGameAudio _bulletLaunch;
        IGameAudio _explosion;

        private List<ISpriteImage> _invaders;
        private IInputManager  _lastState;

        public Game(IGameEngine engine)
        {
            Engine = engine;
            Engine.ContentManager.ContentLocation = "Content";
            Engine.DiagnosticsFontColor = Color.Yellow;
            Engine.Display.BackgroundColor = Color.Black;

            _titleFont = Engine.AssetFactory.CreateFont();
            _titleFont.Name = "Arial";
            _titleFont.Size = 24.0f;
            _titleFont.Bold = true;

            _score = 0;
            _round = 1;
            _invaders = new List<ISpriteImage>();
            _enemyBullets = new List<ISpriteImage>();
            _gameState = GameStates.StartUp;
        }

        public void Initialize() { }
        public void StartGame() { }

        public void BeginRun()
        {
            _gameState = GameStates.StartUp;
            Engine.Pause();
        }

        public void CreateMenus()
        {
            _mainMenu = Engine.MenuManager.Create("Main Menu");
            _mainMenu.MenuSelected += menuComponent_MenuSelected;
            _mainMenu.AddMenu("Continue", INT_CONTINUE);
            _mainMenu.AddMenu("Restart", INT_RESTART);
            _diagnostics = _mainMenu.AddMenu("Turn On Diagnostics", INT_DIAGNOSTICS);
            _mainMenu.AddMenu("Credits", INT_CREDITS);
            _mainMenu.AddMenu("Help", INT_HELP);
            _mainMenu.AddMenu("Exit", INT_EXIT);

            Engine.RegisterComponent(_mainMenu);
        }

        void menuComponent_MenuSelected(object sender, WiMo.Games.Menus.SelectedMenuEventArgs e)
        {
            switch (e.SelectedMenu.MenuId)
            {
                case INT_RESTART:
                    ResetEnemies();
                    _mainMenu.Hide();
                    Engine.Resume();
                    _score = 0;
                    _gameState = GameStates.Active;
                    break;
                case INT_HELP:
                    _gameState = GameStates.Help;
                    _mainMenu.Hide();
                    break;
                case INT_CREDITS:
                    _gameState = GameStates.Credits;
                    _mainMenu.Hide();
                    break;
                case INT_CONTINUE:
                    Engine.Resume();
                    _mainMenu.Hide();
                    break;
                case INT_DIAGNOSTICS:
                    _mainMenu.Hide();
                    Engine.EnableDiagnostics = !Engine.EnableDiagnostics;
                    _diagnostics.Text = (Engine.EnableDiagnostics) ? "Turn Off Diagnostics" : "Turn On Diagnostics";
                    Engine.Resume();
                    break;
                case INT_EXIT:
                    Engine.Exit();
                    break;
            }
        }

        private void ResetEnemies()
        {
            foreach(var bullet in _enemyBullets)
            {
                bullet.IsVisible = false;
                bullet.Position = new PointF(0,0);
            }

            for (var row = 0; row < INT_INVADER_ROWS; ++row)
            {
                for (var idx = 0; idx < INT_INVADER_COLUMNS; ++idx)
                {
                    var invader = _invaders[row * INT_INVADER_COLUMNS + idx];
                    invader.CollisionList.Clear();
                    invader.Position = new PointF(15 + (idx) * (invader.Size.Width + 4), INT_INVADER_TOP + INT_INVADER_SPACING * row);
                    invader.IsVisible = true;
                    invader.Force = new Vector2(INT_ENEMY_BULLET_SPEED, 0);
                }
            }
        }

        private void LoadEnemyBullets()
        {
            for (var blt = 0; blt < INT_CONCURRENT_BULLETS; ++blt)
            {
                var bullet = Engine.ContentManager.LoadImage("EnemyBullet.png");
                bullet.IsVisible = false;
                bullet.Collision += enemyBulletCollide;
                bullet.CanCollidesWithList.Add(CollisionCategory.Two);
                _enemyBullets.Add(bullet);

                Engine.PhysicsManager.Add(bullet);
                Engine.CollisionManager.Add(bullet);
            }
        }

        private void LoadInvaders()
        {
            for (var row = 0; row < INT_INVADER_ROWS; ++row)
            {
                for (var idx = 0; idx < INT_INVADER_COLUMNS; ++idx)
                {
                    var invader = Engine.ContentManager.LoadImage("Invader1.png");
                    invader.Position = new PointF(15 + (idx) * (invader.Size.Width + 4), INT_INVADER_TOP + INT_INVADER_SPACING * row);
                    invader.Force = new Vector2(INT_ENEMY_SPEED, 0);
                    invader.CollisionCategory = CollisionCategory.One;
                    _invaders.Add(invader);

                    Engine.CollisionManager.Add(invader);
                    Engine.PhysicsManager.Add(invader);
                }
            }
        }

        private void LaunchBullet()
        {
            _bullet.Position = new PointF(_ship.Position.X, Engine.Display.Size.Height - (_ship.Size.Height + 8 + _bullet.Size.Height / 2));
            _bullet.ApplyForce(new Vector2(0, -INT_SHIP_BULLET_SPEED));

            _bulletLaunch.Play();
            Engine.Vibrate(20);
        }

        private void ResetBullet()
        {
            _bullet.Position = new PointF(_ship.Position.X, Engine.Display.Size.Height - (_ship.Size.Height + 8 + _bullet.Size.Height / 2));
            _bullet.Force = Vector2.Zero;
        }

        private void LoadShip()
        {
            _ship = Engine.ContentManager.LoadImage("Ship.png");
            _ship.CollisionCategory = CollisionCategory.Two;
            _ship.Position = new PointF(Engine.Display.Size.Width / 2, Engine.Display.Size.Height - ((_ship.Size.Height / 2) + 8));
            Engine.CollisionManager.Add(_ship);

            _bullet = Engine.ContentManager.LoadImage("Bullet.png");
            _bullet.Position = new PointF(_ship.Position.X, Engine.Display.Size.Height - (_ship.Size.Height + 8 + _bullet.Size.Height / 2));
            _bullet.Collision += shipBulletCollide;
            _bullet.CanCollidesWithList.Add(CollisionCategory.One);

            Engine.CollisionManager.Add(_bullet);
            Engine.PhysicsManager.Add(_bullet);
        }

        public void LoadContent()
        {
            _spashImage = Engine.ContentManager.LoadImage("Splash.png");
            _spashImage.Position = new PointF(120, 160);

            LoadShip();
            LoadEnemyBullets();
            LoadInvaders();

            _bulletLaunch = Engine.ContentManager.LoadAudio("blip.wav");
            _explosion = Engine.ContentManager.LoadAudio("Explosion.wav");
        }

        void enemyBulletCollide(object sender, CollisionEventArgs e)
        {
            var enemyBullet = sender as ISprite;
            switch (e.CollisionType)
            {
                case CollisionEventArgs.CollisionTypes.Border:
                    enemyBullet.IsVisible = false;
                    break;
                case CollisionEventArgs.CollisionTypes.Sprite:
                    _gameState = GameStates.GameOver;
                    Engine.Pause();
                    break;
            }
        }

        void shipBulletCollide(object sender, CollisionEventArgs e)
        {
            var bullet = sender as ISprite;

            switch (e.CollisionType)
            {
                case CollisionEventArgs.CollisionTypes.Border:
                    ResetBullet();

                    _bullet.Position = new PointF(_ship.Position.X, Engine.Display.Size.Height - (_ship.Size.Height + 8 + _bullet.Size.Height / 2));
                    _bullet.Force = Vector2.Zero;

                    break;
                case CollisionEventArgs.CollisionTypes.Sprite:
                    ResetBullet();                    
                    e.Sprite.IsVisible = false;
                    e.Sprite.Force = Vector2.Zero;
                    _score += 20;
                    Engine.Vibrate(20);
                    _explosion.Play();
                    break;
            }
        }

        private void CheckForInputs()
        {
            switch (_gameState)
            {
                case GameStates.StartUp:
                    if (Engine.InputManager.WasPressed(_lastState, Keys.Return) 
                        || Engine.InputManager.WasScreenTouched(_lastState))
                    {
                        _score = 0;
                        ResetEnemies();
                        _gameState = GameStates.Active;
                        Engine.Resume();
                    }

                    break;
                case GameStates.BetweenRounds:
                    if (Engine.InputManager.WasPressed(_lastState, Keys.Return)
                        || Engine.InputManager.WasScreenTouched(_lastState)) 
                    {
                        ResetEnemies();
                        _gameState = GameStates.Active;
                        Engine.Resume();
                    }
                    break;
                case GameStates.Active:
                    if (Engine.InputManager.WasPressed(_lastState, Keys.F1))
                    {
                        Engine.Pause();
                        _mainMenu.Reset();
                        _mainMenu.Show();
                    }

                    if (Engine.InputManager.IsPressed(_lastState, Keys.Left, 20))
                    {
                        _ship.Move(new Vector2(-INT_SHIP_MOVEMENT, 0));
                        if (_bullet.Force == Vector2.Zero)
                            _bullet.Position = new PointF(_ship.Position.X, _bullet.Position.Y);
                    }

                    if (Engine.InputManager.IsPressed(_lastState, Keys.Right, 20))
                    {
                        _ship.Move(new Vector2(INT_SHIP_MOVEMENT, 0));
                        if (_bullet.Force == Vector2.Zero)
                            _bullet.Position = new PointF(_ship.Position.X, _bullet.Position.Y);
                    }

                    if (Engine.InputManager.WasPressed(_lastState, Keys.Return) && _bullet.Force == Vector2.Zero)
                    {
                        LaunchBullet();
                    }
                    break;
                case GameStates.GameOver:
                case GameStates.Credits:
                case GameStates.Help:
                    if (Engine.InputManager.WasPressed(_lastState, Keys.F1))
                    {
                        Engine.Pause();
                        _mainMenu.Reset();
                        _mainMenu.Show();
                    }
                    break;
            }

            _lastState = Engine.InputManager.GetKeysState();
        }

        public void Update(GameTime gameTime)
        {
            CheckForInputs();

            switch (_gameState)
            {
                case GameStates.StartUp:

                    break;
                case GameStates.Active:
                    bool allInvadersDestroyed = true;
                    foreach (var invader in _invaders)
                    {
                        if (invader.IsVisible)
                        {
                            allInvadersDestroyed = false;
                            if (Engine.Randomizer.Next(10) == 2)
                            {
                                foreach (var bullet in _enemyBullets)
                                {
                                    if (!bullet.IsVisible)
                                    {
                                        bullet.Position = new PointF(invader.Position.X, invader.Position.Y + invader.Size.Height / 2 + bullet.Size.Height / 2);
                                        bullet.Force = new Vector2(0, INT_ENEMY_BULLET_SPEED);
                                        bullet.IsVisible = true;
                                        break;
                                    }
                                }
                            }
                        }
                    }

                    if (allInvadersDestroyed)
                    {
                        _round++;
                        Engine.Pause();
                        _gameState = GameStates.BetweenRounds;
                    }
                    break;
            }
        }

        public void Draw(GameTime gameTime)
        {
            switch (_gameState)
            {
                case GameStates.StartUp:
                    Engine.Display.Add(_spashImage);
                    break;

                case GameStates.Active:
                    Engine.Display.DrawText(new PointF(2, 3), string.Format("Score {0}", _score), Engine.DefaultFont, Color.White);
                    Engine.Display.DrawText(new PointF(120, 3), "Round " + _round, Engine.DefaultFont, Color.White);
                    Engine.Display.Add(_ship);
                    Engine.Display.Add(_bullet);

                    foreach (var invader in _invaders)
                    {
                        if (invader.IsVisible)
                            Engine.Display.Add(invader);
                    }

                    foreach (var bullet in _enemyBullets)
                    {
                        if (bullet.IsVisible)
                            Engine.Display.Add(bullet);
                    }

                    break;
                case GameStates.BetweenRounds:
                    Engine.Display.DrawText(new PointF(30, 50), "Round " + _round, _titleFont, Color.Yellow);
                    Engine.Display.DrawText(new PointF(40, 100), string.Format("Score {0}", _score), _titleFont, Color.White);
                    break;
                case GameStates.GameOver:
                    Engine.Display.DrawText(new PointF(40, 100), string.Format("Score {0}", _score), _titleFont, Color.White);
                    Engine.Display.DrawText(new PointF(30, 50), "Game Over", _titleFont, Color.Yellow);
                    Engine.Display.DrawText(new PointF(5, 300), "Main Menu", Engine.DefaultFont, Color.White);
                    break;
                case GameStates.Help:
                    Engine.Display.DrawText(new PointF(20, 3), "Invaders\r\nFrom Space", _titleFont, Color.Yellow);
                    string help = @"
User the left and right buttons
of your thumb pad to move your
ship.

Use the center button to 
destroy the enemy.
";
                    Engine.Display.DrawText(new PointF(20, 110), help, Engine.DefaultFont, Color.White);
                    Engine.Display.DrawText(new PointF(5, 300), "Main Menu", Engine.DefaultFont, Color.White);
                    break;
                case GameStates.Credits:
                    Engine.Display.DrawText(new PointF(10, 3), "Invaders\r\nFrom Space", _titleFont, Color.Yellow);
                    string credits = @"
Created By: Shaken' Game Studios
   http://www.Shaken-GS.com
Programming: Kevin D. Wolf
       aka @ByteMaster

Built with WiMo Game Engine
";
                    Engine.Display.DrawText(new PointF(20, 110), credits, Engine.DefaultFont, Color.White);
                    Engine.Display.DrawText(new PointF(5, 300), "Main Menu", Engine.DefaultFont, Color.White);
                    break;
            }
        }

        public bool CanExit()
        {
            Engine.Exit();

            return true;
        }

        public IGameEngine Engine { get; set; }
    }
}
